package com.eascs.site.session;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.concurrent.TimeoutException;

import javax.annotation.PostConstruct;

import com.eascs.app.cache.CacheException;
import com.eascs.app.lang.TimeLength;
import com.eascs.app.log.Logger;
import com.eascs.app.log.LoggerFactory;
import com.eascs.app.monitor.status.ServiceStatusMonitor;
import com.eascs.app.monitor.status.ServiceStatus;
import com.eascs.common.assertion.util.AssertErrorUtils;
import com.eascs.common.util.StopWatch;
import com.eascs.web.context.WebSetting;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.utils.AddrUtil;

public class MemcachedSessionProvider implements SessionProvider, ServiceStatusMonitor {
	private final Logger logger = LoggerFactory.getLogger(MemcachedSessionProvider.class);

	private static final TimeLength MEMCACHED_TIME_OUT = TimeLength.seconds(3);
	private MemcachedClient memcachedClient;

	@Override
	public SessionData loadSessionData(String id, String ip) {
		SessionData sessionData = null;
		String sessionKey = getCacheKey(id, ip);
		StopWatch watch = new StopWatch();
		try {
			sessionData = memcachedClient.getAndTouch(sessionKey, expirationTime());
			if (null != sessionData && sessionData.getMaxInactiveInterval() != expirationTime()) {
				logger.debug("update MaxInactiveInterval from {} to {}", expirationTime(), sessionData.getMaxInactiveInterval());
				sessionData = memcachedClient.getAndTouch(sessionKey, sessionData.getMaxInactiveInterval());
			}
		} catch (TimeoutException | InterruptedException | MemcachedException e) {
			throw new CacheException(e);
		} finally {
			logger.debug("loadSessionData, memcachedKey={},hit={}, elapsedTime={}", sessionKey, null != sessionData, watch.elapsedTime());
		}
		return sessionData;
	}

	@Override
	public void updateSessionData(SessionData sessionData) {
		AssertErrorUtils.assertNotNull(sessionData.getId(), "Session id is null");
		String sessionKey = getCacheKey(sessionData.getId(), sessionData.getIp());
		StopWatch watch = new StopWatch();
		try {
			memcachedClient.set(sessionKey, sessionData.getMaxInactiveInterval(), sessionData);
		} catch (TimeoutException | InterruptedException | MemcachedException e) {
			throw new CacheException(e);
		} finally {
			logger.debug("updateSessionData, memcachedKey={},expirationTime={}, elapsedTime={}", sessionKey, expirationTime(), watch.elapsedTime());

		}
	}

	private String getCacheKey(String sessionId, String ip) {
		return new StringBuffer("session:").append(sessionId).toString();
	}

	@PostConstruct
	private void initMemcachedClient() {
		MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(WebSetting.get().getSessionServers()));
		builder.setCommandFactory(new BinaryCommandFactory());
		builder.setOpTimeout(MEMCACHED_TIME_OUT.toMilliseconds());
		try {
			memcachedClient = builder.build();
			AssertErrorUtils.assertNotNull(memcachedClient, "memcachedClient init fail!");
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
		}
	}

	private int expirationTime() {
		return WebSetting.get().getSessionTimeOut();
	}

	@Override
	public SessionData createSessionData(String id, String ip) {
		SessionData sessionData = new SessionData(id, ip);
		sessionData.setCreationTime(System.currentTimeMillis());
		sessionData.setMaxInactiveInterval(this.expirationTime());
		return sessionData;
	}

	@Override
	public ServiceStatus getServiceStatus() throws Exception {
		Collection<InetSocketAddress> availableServers = memcachedClient.getAvailableServers();
		return availableServers.isEmpty() ? ServiceStatus.DOWN : ServiceStatus.UP;
	}

	@Override
	public String getServiceName() {
		return "Memcached Sessions";
	}

	public MemcachedSessionProvider() {
		super();
		logger.debug("init:{}", this.getClass().getName());
	}

}
